# -*- coding: utf-8 -*-

import sys
import struct
import socket
import select

from error import ClientErr
from utils import print_t, print_err


class TcpServer(object):

    MAX_RECV_BUFFER = 65536

    def __init__(self, port, addr='0.0.0.0'):
        super(TcpServer, self).__init__()
        self._addr_port = (addr, port)
        self._sock = None
        self._fd_list = []
        self._sock_buff = {}         # conn : buff

    #     self._evt_recv = None
    #     self._evt_offline = None
    #     self._evt_tick = None
    #
    # def bind_event_recv(self, func):    # (conn, data)
    #     self._evt_recv = func
    #
    # def bind_event_offline(self, func):
    #     self._evt_offline = func
    #
    # def bind_event_tick(self, func):
    #     self._evt_tick = func

    def recv_proc(self, conn, data):
        print("TcpServer recv_proc")

    def offline_proc(self, conn):
        print("TcpServer offline_proc")

    def __tick_proc(self):
        pass

    def run(self, tick=1.0):
        try:
            self._init_socket()
        except socket.error:
            sys.exit(1)

        self._fd_list = [self._sock]
        while True:
            r_list, w_list, e_list = select.select(self._fd_list, [], self._fd_list, tick)
            for sk in r_list:
                if sk == self._sock:
                    conn, address = sk.accept()
                    self._fd_list.append(conn)
                    print_t("Accept from {}".format(address))
                else:
                    try:
                        self._client_proc(sk)
                    except ClientErr as e:
                        print_err(e)
                        self.close_conn(sk)

            for sk in e_list:
                self.close_conn(sk)

            if not (r_list or w_list or e_list):
                try:
                    self.__tick_proc()
                except ClientErr as e:
                    print_err(e)

    @staticmethod
    def send(conn, data_str):
        packet = struct.pack('i', len(data_str) + 4) + data_str
        print("Send packet: {}".format(repr(packet)))
        try:
            conn.sendall(packet)
        except socket.error:
            raise ClientErr("Send message failed. Connection has closed")

    def close_conn(self, conn):
        if conn in self._fd_list:
            try:
                self.offline_proc(conn)
            except ClientErr as e:
                print_err(e)
            if conn in self._sock_buff:
                del(self._sock_buff[conn])
            self._fd_list.remove(conn)
        conn.close()

    def _init_socket(self):
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
        self._sock.bind(self._addr_port)
        self._sock.listen(0)

    def _client_proc(self, conn):
        try:
            curr_packet = conn.recv(TcpServer.MAX_RECV_BUFFER)
        except socket.error:
            self.close_conn(conn)
            return
        if not curr_packet:
            self.close_conn(conn)
            return

        print("Recv packet: {}".format(repr(curr_packet)))
        self._recv_proc(conn, curr_packet)

    def _recv_proc(self, conn, curr_packet):
        if conn not in self._sock_buff:
            full_packet = curr_packet
        else:
            full_packet = self._sock_buff[conn] + curr_packet

        pos = 0
        while True:
            msg_len = TcpServer._get_msg_len(full_packet, pos)
            if msg_len < 0 or pos + msg_len > len(full_packet):    # 消息不完整
                break
            else:
                self.recv_proc(conn, full_packet[pos + 4: pos + msg_len])
                pos += msg_len
        self._sock_buff[conn] = full_packet[pos:]

    @staticmethod
    def _get_msg_len(packet, pos=0):
        if len(packet) < pos + 4:
            return -1
        return struct.unpack('i', packet[pos: pos + 4])[0]


# def test1():
#
#     def recv_proc(conn, data):
#         print("#Recv Msg: {}".format(repr(data)))
#         TcpServer.send(conn, data)
#
#     s = TcpServer()
#     s.bind_event_recv(recv_proc)
#     s.run()


def test1():

    class TestServer(TcpServer):
        def recv_proc(self, conn, data):
            print("#Recv Msg: {}".format(repr(data)))
            TcpServer.send(conn, data)

    TestServer(55340).run()


if __name__ == '__main__':
    test1()

